﻿using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Xml.Linq;
using System.Linq;
using System.Diagnostics;

namespace System.Windows.Forms
{
    #region Delegates

    public delegate void OpenFileDelegate(string path);
    public delegate void OpenFolderDelegate(string path);

    #endregion 

    public partial class FileExplorerNet : UserControl
    {
        #region Fields

        private ImageList imageList;
        private IconListManager iconListManager;
        private Dictionary<string, string> favouritesList;
        private TreeNode nodeAtCursorLocation;
        private OpenFileDelegate openFileDelegate;
        private OpenFolderDelegate openFolderDelegate;
        private bool showFavourites;
        
        #endregion


        #region Properties

        /// <summary>
        /// Gets or sets a value indicating whether to show the favourites panel.
        /// </summary>
        /// <value><c>true</c> if [show favourites]; otherwise, <c>false</c>.</value>
        public bool ShowFavourites
        {
            get
            {
                return showFavourites;
            }
            set
            {
                showFavourites = value;
            }
        }

        /// <summary>
        /// Gets the favourites count.
        /// </summary>
        /// <value>The favourites count.</value>
        public int FavouritesCount
        {
            get
            {
                return lstFavourites.Items.Count;
            }
        }

        /// <summary>
        /// Gets or sets the favourites list.
        /// </summary>
        /// <value>The favourites list.</value>
        public IDictionary<string, string> FavouritesList
        {
            get
            {
                LoadFavouritesList();
                return favouritesList;
            }
            set
            {
                favouritesList = (Dictionary<string, string>)value;
                UpdateFavouritesListView();
            }
        }

        /// <summary>
        /// Gets the selected text in the treeview.
        /// </summary>
        /// <value>The selected text.</value>
        public string SelectedText
        {
            get
            {
                return treExplorer.SelectedNode.Text;
            }
        }

        /// <summary>
        /// Gets the selected node in the treeview.
        /// </summary>
        /// <value>The selected node.</value>
        public TreeNode SelectedNode
        {
            get
            {
                return treExplorer.SelectedNode;
            }
        }

        /// <summary>
        /// Sets the open file delegate.
        /// </summary>
        /// <value>The open file delegate.</value>
        public OpenFileDelegate OpenFileDelegate
        {
            set
            {
                openFileDelegate = value;
            }
        }

        /// <summary>
        /// Gets or sets the open folder delegate.
        /// </summary>
        /// <value>The open folder delegate.</value>
        public OpenFolderDelegate OpenFolderDelegate
        {
            get
            {
                return openFolderDelegate;
            }
            set
            {
                openFolderDelegate = value;
            }
        }

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="FileExplorerNet"/> class.
        /// </summary>
        public FileExplorerNet()
        {
            InitializeComponent();
        }

        #endregion

        #region Events

        /// <summary>
        /// Handles the LostFocus event of the treExplorer control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void treExplorer_LostFocus(object sender, EventArgs e)
        {
            tipMessage.RemoveAll();
        }

        /// <summary>
        /// Handles the NodeMouseHover event of the treExplorer control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.TreeNodeMouseHoverEventArgs"/> instance containing the event data.</param>
        private void treExplorer_NodeMouseHover(object sender, TreeNodeMouseHoverEventArgs e)
        {
            HandleTreeViewNodeMouseHover(e);
        }

        /// <summary>
        /// Handles the Resize event of the FileExplorerNet control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void FileExplorerNet_Resize(object sender, EventArgs e)
        {
            Refresh();
        }

        /// <summary>
        /// Handles the NodeMouseDoubleClick event of the treExplorer control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.TreeNodeMouseClickEventArgs"/> instance containing the event data.</param>
        private void treExplorer_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            HandleTreeViewNodeMouseDoubleClick();
        }

        /// <summary>
        /// Handles the BeforeExpand event of the tvExplorer control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.TreeViewCancelEventArgs"/> instance containing the event data.</param>
        private void tvExplorer_BeforeExpand(object sender, TreeViewCancelEventArgs e)
        {
            HandleTreeViewExpanding(e);
        }

        /// <summary>
        /// Handles the MouseDown event of the tvExplorer control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.MouseEventArgs"/> instance containing the event data.</param>
        private void tvExplorer_MouseDown(object sender, MouseEventArgs e)
        {
            HandleTreeViewMouseDown(e);
        }

        /// <summary>
        /// Handles the AfterSelect event of the treExplorer control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.TreeViewEventArgs"/> instance containing the event data.</param>
        private void treExplorer_AfterSelect(object sender, TreeViewEventArgs e)
        {
            HandleAfterSelect(e.Node);
        }

        /// <summary>
        /// Handles the KeyDown event of the treExplorer control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.KeyEventArgs"/> instance containing the event data.</param>
        private void treExplorer_KeyDown(object sender, KeyEventArgs e)
        {
            HandleKeyDown(e);
        }

        /// <summary>
        /// Handles the Click event of the btnExpandAll control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void btnExpandAll_Click(object sender, EventArgs e)
        {
            treExplorer.ExpandAll();
        }

        /// <summary>
        /// Handles the Click event of the btnCollapse control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void btnCollapse_Click(object sender, EventArgs e)
        {
            treExplorer.CollapseAll();
        }

        /// <summary>
        /// Handles the Click event of the btnRefresh control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void btnRefresh_Click(object sender, EventArgs e)
        {
            UpdateNode(treExplorer.SelectedNode, true);
        }

        /// <summary>
        /// Handles the Click event of the btnAddFavourite control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void btnAddFavourite_Click(object sender, EventArgs e)
        {
            AddFavourite();
        }

        /// <summary>
        /// Handles the ItemActivate event of the lstFavourites control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void lstFavourites_ItemActivate(object sender, EventArgs e)
        {
            HandleFavouriteActivation();
        }

        /// <summary>
        /// Handles the Click event of the cmnuFavouritesListDeleteAll control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void cmnuFavouritesListDeleteAll_Click(object sender, EventArgs e)
        {
            DeleteFavouriteItems(true, null);
        }

        /// <summary>
        /// Handles the Click event of the cmnuFavouriteDeleteSelected control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void cmnuFavouriteDeleteSelected_Click(object sender, EventArgs e)
        {
            DeleteSelectedFavourites();
        }

        /// <summary>
        /// Handles the CheckedChanged event of the cbShowFavourites control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void cbShowFavourites_CheckedChanged(object sender, EventArgs e)
        {
            CollapseBookmarkList();
        }

        #endregion

        #region Methods

        #region Public

        /// <summary>
        /// Loads this instance.
        /// IMPORTANT: User must call this method manually
        /// </summary>
        public void Load()
        {
            Initialize();

            if (favouritesList.Count > 0)
            {
                UpdateFavouritesListView();
            }
        }

        /// <summary>
        /// Saves all user favourite paths to a file
        /// </summary>
        public void SaveFavourites()
        {
            XElement favourites = new XElement("favourites");
            XElement child = null;
            foreach (KeyValuePair<string, string> fav in favouritesList)
            {
                child = new XElement("favourite");
                child.SetAttributeValue("name", fav.Key);
                child.SetAttributeValue("path", fav.Value);

                favourites.Add(child);
            }

            favourites.Save("favourites.xml");
        }

        #endregion

        #region Private

        /// <summary>
        /// Initializes this instance.
        /// IMPORTANT: Must be called manually by the user.
        /// </summary>
        private void Initialize()
        {
            this.Resize += new EventHandler(FileExplorerNet_Resize);
            treExplorer.NodeMouseDoubleClick += new TreeNodeMouseClickEventHandler(treExplorer_NodeMouseDoubleClick);
            treExplorer.NodeMouseHover += new TreeNodeMouseHoverEventHandler(treExplorer_NodeMouseHover);
            treExplorer.LostFocus += new EventHandler(treExplorer_LostFocus);
            treExplorer.BeforeExpand += tvExplorer_BeforeExpand;

            imageList = new ImageList();
            favouritesList = new Dictionary<string, string>();
            spcMain.Panel1.BackColor = Color.Transparent;
            spcMain.Panel2.BackColor = Color.Transparent;
            lblBookmarks.BackColor = Color.Transparent;
            imageList.ColorDepth = ColorDepth.Depth32Bit;
            imageList.ImageSize = new System.Drawing.Size(16, 16);
            iconListManager = new IconListManager(imageList, IconReader.IconSize.Small);
            
            treExplorer.ImageList = imageList;
            treExplorer.ImageIndex = -1;
            
            if (File.Exists("images\\Refresh.bmp"))
                btnRefresh.Image = Image.FromFile("images\\Refresh.bmp");
            if (File.Exists("images\\Expand_large.bmp"))
                btnExpandAll.Image = Image.FromFile("images\\Expand_large.bmp");
            if (File.Exists("images\\Collapse_large.bmp"))
                btnCollapse.Image = Image.FromFile("images\\Collapse_large.bmp");
            if (File.Exists("images\\AddToFavoritesHS.png"))
                btnAddBookmark.Image = Image.FromFile("images\\AddToFavoritesHS.png");

            tipMessage.UseFading = true;

            LoadDrives();
            InitializeFavouritesList();

            cbShowFavourites.Checked = showFavourites;
        }
        
        /// <summary>
        /// If the favourites.xml file exists, initializes the internal favourite's dictionary with it's contents.
        /// </summary>
        private void InitializeFavouritesList()
        {
            if (File.Exists("favourites.xml"))
            {
                XElement favourites = XElement.Load("favourites.xml");
                foreach (XElement fav in favourites.Elements("favourite"))
                {
                    favouritesList.Add(fav.Attribute("name").Value, fav.Attribute("path").Value);
                }
            }
        }

        

        /// <summary>
        /// Handles the TreeNode expanding
        /// </summary>
        /// <param name="e"></param>
        private void HandleTreeViewExpanding(TreeViewCancelEventArgs e)
        {
            e.Node.ImageIndex = imageList.Images.IndexOfKey("FolderOpened");
            HandleAfterSelect(e.Node);
        }

        /// <summary>
        /// Handles the tree view mouse down.
        /// </summary>
        /// <param name="e">The <see cref="System.Windows.Forms.MouseEventArgs"/> instance containing the event data.</param>
        private void HandleTreeViewMouseDown(MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right)
            {
                if (nodeAtCursorLocation != null)
                    treExplorer.SelectedNode = nodeAtCursorLocation;
                TreeNode selectedNode = treExplorer.SelectedNode;

                if (selectedNode.Tag != null)
                {
                    ShellContextMenu ctxMnu = new ShellContextMenu();

                    if ((File.Exists(selectedNode.Tag.ToString())))
                    {
                        ctxMnu.ShowContextMenu(new FileInfo[] { new FileInfo(selectedNode.Tag.ToString()) },
                            PointToScreen(new Point(e.X, e.Y)));
                    }
                    else if (Directory.Exists(selectedNode.Tag.ToString()))
                    {
                        ctxMnu.ShowContextMenu(new DirectoryInfo[] { new DirectoryInfo(selectedNode.Tag.ToString()) },
                            PointToScreen(new Point(e.X, e.Y)));
                    }

                    UpdateNode(treExplorer.SelectedNode, true);
                }
            }
        }

        /// <summary>
        /// Handles the favourite activation.
        /// </summary>
        private void HandleFavouriteActivation()
        {
            string path = lstFavourites.SelectedItems[0].SubItems[1].Text;

            if (File.Exists(path))
            {
                if (openFileDelegate == null)
                    OpenFile(path);
                else
                    openFileDelegate.Invoke(path);
            }
            else if (Directory.Exists(path))
            {
                if (openFolderDelegate == null)
                    OpenFolderInExplorer(path);
                else
                    openFolderDelegate(path);
            }
        }

        /// <summary>
        /// Handles the tree view node mouse double click.
        /// </summary>
        private void HandleTreeViewNodeMouseDoubleClick()
        {
            string path = treExplorer.SelectedNode.Tag.ToString();

            if (File.Exists(path))
            {
                if (openFileDelegate == null)
                    OpenFile(path);
                else
                    openFileDelegate(path);
            }
        }

        /// <summary>
        /// Handles the tree view node mouse hover.
        /// </summary>
        /// <param name="e">The <see cref="System.Windows.Forms.TreeNodeMouseHoverEventArgs"/> instance containing the event data.</param>
        private void HandleTreeViewNodeMouseHover(TreeNodeMouseHoverEventArgs e)
        {
            tipMessage.ToolTipTitle = e.Node.Text;
            nodeAtCursorLocation = e.Node;
        }

        /// <summary>
        /// Deletes the favourite items.
        /// </summary>
        /// <param name="deleteAll">if set to <c>true</c>deletes all</param>
        /// <param name="indexOfItemsToDelete">The treenode indexes to delete.</param>
        private void DeleteFavouriteItems(bool deleteAll, int[] indexOfItemsToDelete)
        {
            if (deleteAll)
            {
                favouritesList.Clear();
            }
            else if (indexOfItemsToDelete != null && indexOfItemsToDelete.Length > 0)
            {
                int indexToDelete;
                for (int i = 0; i < indexOfItemsToDelete.Length; i++)
                {
                    indexToDelete = indexOfItemsToDelete[i];
                    favouritesList.Remove(lstFavourites.Items[indexToDelete].Text);
                    //lstBookmarks.Items.RemoveAt(indexToDelete);
                }
            }
            UpdateFavouritesListView();
        }

        /// <summary>
        /// Deletes the selected favourites.
        /// </summary>
        private void DeleteSelectedFavourites()
        {
            ListView.SelectedIndexCollection selectedIndices = lstFavourites.SelectedIndices;
            int[] itemsToDelete = new int[selectedIndices.Count];
            for (int i = 0; i < selectedIndices.Count; i++)
                itemsToDelete[i] = selectedIndices[i];
            DeleteFavouriteItems(false, itemsToDelete);
        }

        /// <summary>
        /// Opens the folder in Windows Eplorer.
        /// 
        /// </summary>
        /// <param name="path">The path. If it's a file, then it'll be selected in the explorer window</param>
        private void OpenFolderInExplorer(string path)
        {
            if (path == null) return;
            string arguments = path;
            if (File.Exists(path)) arguments = "/select," + path;

            Process.Start("explorer.exe", arguments);
        }

        /// <summary>
        /// Loads all logical drives into the treeview.
        /// </summary>
        private void LoadDrives()
        {
            string[] drives = Environment.GetLogicalDrives();
            TreeNode[] driveNodes = new TreeNode[drives.Length];
            int imageIndex;
            treExplorer.BeginUpdate();

            for (int i = 0; i < drives.Length; i++)
            {
                driveNodes[i] = new TreeNode(drives[i]);
                driveNodes[i].Tag = drives[i];
                imageList.Images.Add(drives[i], IconReader.GetDriveIcon(drives[i]));
                imageIndex = imageList.Images.IndexOfKey(drives[i]);
                driveNodes[i].ImageIndex = imageIndex;
                driveNodes[i].SelectedImageIndex = imageIndex;
                try
                {
                    if (Directory.GetDirectories(drives[i]) != null || Directory.GetFiles(drives[i]) != null)
                        driveNodes[i].Nodes.Add("DUMMY_NODE");
                }
                catch (Exception ex)
                {
                }
            }
            treExplorer.Nodes.AddRange(driveNodes);
            treExplorer.EndUpdate();
        }

        /// <summary>
        /// Updates the node.
        /// </summary>
        /// <param name="node">The node to be updated</param>
        /// <param name="reload">if set to <c>true</c> the node will be reloaded to get the latest changes from the filesystem.</param>
        private void UpdateNode(TreeNode node, bool reload)
        {
            treExplorer.Focus();

            if (node == null)
                return;

            if (reload)
                node.Nodes.Clear();

            if (node.Nodes.Count >= 0)
            {
                string tag = node.Tag.ToString();
                if (tag == null || File.Exists(tag))
                    return;

                string[] folders = null;
                string[] files = null;
                try
                {
                    folders = Directory.GetDirectories(tag);
                    files = Directory.GetFiles(tag);
                }
                catch (Exception ex)    // Crazy exception handling
                {
                    ShowMessage(ex.Message);
                }


                if (folders == null || files == null)
                    return;

                TreeNode[] contents = new TreeNode[folders.Length + files.Length];
                int imageIndex;

                if (!imageList.Images.Keys.Contains("FolderClosed"))
                {
                    imageList.Images.Add("FolderClosed",
                        IconReader.GetFolderIcon(IconReader.IconSize.Small, IconReader.FolderType.Closed));
                }

                if (!imageList.Images.Keys.Contains("FolderOpened"))
                {
                    imageList.Images.Add("FolderOpened",
                        IconReader.GetFolderIcon(IconReader.IconSize.Small, IconReader.FolderType.Open));
                }

                imageIndex = imageList.Images.IndexOfKey("FolderClosed");
                
                // Adding all folders
                for (int i = 0; i < folders.Length; i++)
                {
                    string[] dirs = folders[i].Split(Path.DirectorySeparatorChar);
                    contents[i] = new TreeNode(dirs[dirs.Length - 1], imageIndex, imageIndex);
                    contents[i].Tag = folders[i];

                    try
                    {
                        if (Directory.GetDirectories(folders[i]) != null || Directory.GetFiles(folders[i]) != null)
                            contents[i].Nodes.Add("DUMMY_NODE");
                    }
                    catch (Exception ex)
                    {
                    }
                }

                // Adding all files
                for (int i = folders.Length; i < folders.Length + files.Length; i++)
                {
                    imageIndex = iconListManager.AddFileIcon(files[i - folders.Length]);
                    contents[i] = new TreeNode(Path.GetFileName(files[i - folders.Length]),
                        imageIndex, imageIndex);
                    contents[i].Tag = files[i - folders.Length];

                    try
                    {
                        if (Directory.GetDirectories(files[i - folders.Length]) != null || 
                            Directory.GetFiles(files[i - folders.Length]) != null)
                            contents[i].Nodes.Add("DUMMY_NODE");
                    }
                    catch (Exception ex)
                    {
                    }
                }

                node.Nodes.AddRange(contents);
            }
            RemoveDummyNode(node);
        }

        /// <summary>
        /// Removes the dummy node.
        /// </summary>
        /// <param name="selectedNode">The selected node.
        /// This will be the parent node, having dummynode as a child.</param>
        private static void RemoveDummyNode(TreeNode selectedNode)
        {
            TreeNode dummyNode = (from TreeNode node in selectedNode.Nodes
                                  where node.Text == "DUMMY_NODE"
                                  select node).SingleOrDefault();
            if (dummyNode != null)
                selectedNode.Nodes.Remove(dummyNode);
        }

        /// <summary>
        /// Updates the favourites list view.
        /// </summary>
        private void UpdateFavouritesListView()
        {
            if (favouritesList == null)
                return;
            lstFavourites.Items.Clear();
            foreach (KeyValuePair<string, string> kvp in favouritesList)
            {
                ListViewItem bookmark = new ListViewItem(kvp.Key);
                bookmark.SubItems.Add(kvp.Value);
                lstFavourites.Items.Add(bookmark);
            }
            CollapseBookmarkList();
        }

        /// <summary>
        /// Opens the passed file.
        /// </summary>
        /// <param name="filePath">The file to open.</param>
        private void OpenFile(string filePath)
        {
            if (File.Exists(filePath))
            {
                System.Diagnostics.Process.Start(filePath);
            }
        }

        /// <summary>
        /// Shows the message
        /// </summary>
        /// <param name="message">The message to show as a tipmessage</param>
        private void ShowMessage(string message)
        {
            tipMessage.ToolTipTitle = treExplorer.SelectedNode.Text;

            tipMessage.Show(message.Trim(), treExplorer,
                treExplorer.SelectedNode.Bounds.Left + treExplorer.SelectedNode.Bounds.Width,
                treExplorer.SelectedNode.Bounds.Top, 1000);
        }

        /// <summary>
        /// Handles the after select event of the treeview.
        /// </summary>
        /// <param name="selectedNode">The selected node.</param>
        private void HandleAfterSelect(TreeNode selectedNode)
        {
            treExplorer.BeginUpdate();
            this.treExplorer.Cursor = Cursors.WaitCursor;

            UpdateNode(selectedNode, true);
            
            this.treExplorer.Cursor = Cursors.Default;
            treExplorer.EndUpdate();
        }

        /// <summary>
        /// Handles the key down.
        /// </summary>
        /// <param name="e">The <see cref="System.Windows.Forms.KeyEventArgs"/> instance containing the event data.</param>
        private void HandleKeyDown(KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.F5:
                    UpdateNode(treExplorer.SelectedNode, true);
                    break;
                case Keys.Enter:
                    treExplorer.SelectedNode.Expand();
                    treExplorer.Focus();
                    break;
            }
        }

        /// <summary>
        /// Adds the selected node as favourite.
        /// </summary>
        private void AddFavourite()
        {
            if (treExplorer.SelectedNode != null)
            {
                ListViewItem bookmark = new ListViewItem(treExplorer.SelectedNode.Text);
                string item = treExplorer.SelectedNode.Tag.ToString();

                if (File.Exists(item) || Directory.Exists(item))
                {
                    bookmark.SubItems.Add(item);
                    try
                    {
                        favouritesList.Add(treExplorer.SelectedNode.Text, item);
                        lstFavourites.Items.Add(bookmark);
                    }
                    catch (ArgumentException ex)
                    {
                        ShowMessage(ex.Message);
                    }

                    CollapseBookmarkList();
                }
            }
        }

        /// <summary>
        /// Loads the favourites list.
        /// </summary>
        private void LoadFavouritesList()
        {
            if (favouritesList == null)
                favouritesList = new Dictionary<string, string>();

            if (favouritesList.Count > 0)
                favouritesList.Clear();
            foreach (ListViewItem item in lstFavourites.Items)
                favouritesList.Add(item.Text, item.SubItems[1].Text);

            CollapseBookmarkList();
        }

        /// <summary>
        /// Collapses the bookmark list
        /// </summary>
        private void CollapseBookmarkList()
        {
            spcMain.Panel2Collapsed = !(cbShowFavourites.Checked);
        }

        #endregion

        #endregion
    }
}